package com.google.jessewilson.codesearch.io;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * An immutable relative or absolute path in a filesystem.
 *
 * TODO(jessewilson): add Windows support
 *
 * @author jessewilson
 */
public class Path implements Comparable<Path> {
  final List<String> parts;
  final boolean absolute;
  final boolean directory;
  final String string;

  private Path(String path) {
    directory = path.endsWith("/");
    absolute = path.startsWith("/");
    List<String> partsArray = Arrays.asList(path.split("/"));

    // split might return an extra empty element at the beginning
    if(!partsArray.isEmpty() && partsArray.get(0).length() == 0) {
      partsArray = new ArrayList<String>(partsArray);
      partsArray.remove(0);
    }

    parts = Collections.unmodifiableList(partsArray);
    string = pathToString(this);
  }

  private Path(List<String> parts, boolean absolute, boolean directory) {
    this.parts = Collections.unmodifiableList(new ArrayList<String>(parts));
    this.absolute = absolute;
    this.directory = directory;
    this.string = pathToString(this);
  }

  public List<String> getParts() {
    return parts;
  }

  /**
   * Returns the parent path, or null if this path has no parent.
   */
  public Path getParent() {
    if(parts.size() <= 1) {
      return null;
    }

    return new Path(parts.subList(0, parts.size() - 1), absolute, true);
  }

  public boolean isAbsolute() {
    return absolute;
  }

  public boolean isDirectory() {
    return directory;
  }

  public String getFileName() {
    if (parts.isEmpty()) {
      return "";
    }
    return parts.get(parts.size() - 1);
  }

  public boolean equals(Object other) {
    if(this == other) return true;
    if(other == null || getClass() != other.getClass()) return false;
    Path path = (Path) other;
    if(!parts.equals(path.parts)) return false;
    return true;
  }
  public int hashCode() {
    return parts.hashCode();
  }
  public int compareTo(Path other) {
    if(other == this) {
      return 0;
    }

    // try case insensitive first
    String thisToString = toString();
    String otherToString = other.toString();
    int result = String.CASE_INSENSITIVE_ORDER.compare(thisToString, otherToString);
    if (result != 0) {
      return result;
    }

    // finally try case sensitive
    return thisToString.compareTo(otherToString);
  }

  public String toString() {
    return string;
  }

  /**
   * Parse a path from a String.
   */
  public static Path fromString(String path) {
    return new Path(path);
  }

  /**
   * Convert a path to a String.
   */
  private static String pathToString(Path path) {
    StringBuilder result = new StringBuilder();
    if(path.isAbsolute()) {
      result.append("/");
    }
    for(int p = 0; p < path.parts.size(); p++) {
      if(p != 0) result.append("/");
      result.append(path.parts.get(p));
    }
    if(path.isDirectory()) {
      result.append("/");
    }

    return result.toString();
  }
}
